#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"

#include "sdkconfig.h"
#include "esp_log.h"
#include "spi_w25n01gvzeig.h"

#define DMA_CHAN        2
#define PIN_NUM_MISO    13
#define PIN_NUM_MOSI    11
#define PIN_NUM_CLK     12
#define PIN_NUM_CS      10

#define NAND_CS_LOW()     gpio_set_level(PIN_NUM_CS, 0)//CS LOW                      
#define NAND_CS_HIGH()    gpio_set_level(PIN_NUM_CS, 1)//CS HIGH

static const char TAG[] = "w25n01";
spi_device_handle_t sg_spi;


esp_err_t spi_write(spi_device_handle_t spi, uint8_t *data, uint8_t len)
{
    esp_err_t ret;
    spi_transaction_t t;
    if (len==0) return 0;             //no need to send anything
    memset(&t, 0, sizeof(t));       //Zero out the transaction



    t.length=len*8;                 //Len is in bytes, transaction length is in bits.
    t.tx_buffer=data;               //Data
    t.user=(void*)1;                //D/C needs to be set to 1
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.


    return ret;
}

esp_err_t spi_read(spi_device_handle_t spi, uint8_t *data)
{
    spi_transaction_t t;

    //gpio_set_level(PIN_NUM_CS, 0);

    memset(&t, 0, sizeof(t));
    t.length=8;
    t.flags = SPI_TRANS_USE_RXDATA;
    t.user = (void*)1;
    esp_err_t ret = spi_device_polling_transmit(spi, &t);
    assert( ret == ESP_OK );
    *data = t.rx_data[0];

    //gpio_set_level(PIN_NUM_CS, 1);

    return ret;
}



void spi_nand_init(void)
{
    esp_err_t ret;
    ESP_LOGD(TAG, "Initializing bus SPI%d...", SPI2_HOST+1);

    spi_bus_config_t buscfg={
        .miso_io_num = PIN_NUM_MISO,                // MISO信号线
        .mosi_io_num = PIN_NUM_MOSI,                // MOSI信号线
        .sclk_io_num = PIN_NUM_CLK,                 // SCLK信号线
        .quadwp_io_num = -1,                        // WP信号线，专用于QSPI的D2
        .quadhd_io_num = -1,                        // HD信号线，专用于QSPI的D3
        .max_transfer_sz = 64*8,                    // 最大传输数据大小
    };

    spi_device_interface_config_t devcfg={
        .clock_speed_hz = SPI_MASTER_FREQ_10M,      // Clock out at 10 MHz,
        .mode = 0,                                  // SPI mode 0
        /*
         * The timing requirements to read the busy signal from the EEPROM cannot be easily emulated
         * by SPI transactions. We need to control CS pin by SW to check the busy signal manually.
         */
        .spics_io_num = -1,
        .queue_size = 7,                            // 传输队列大小，决定了等待传输数据的数量
    };

    //Initialize the SPI bus
    ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
    ESP_ERROR_CHECK(ret);
    ret = spi_bus_add_device(SPI2_HOST, &devcfg, &sg_spi);
    ESP_ERROR_CHECK(ret);

    gpio_pad_select_gpio(PIN_NUM_CS);                // 选择一个GPIO
    gpio_set_direction(PIN_NUM_CS, GPIO_MODE_OUTPUT);// 把这个GPIO作为输出

}


int spi_sendbyte(uint8_t byte)
{                
    esp_err_t ret = 0;
    ret = spi_write(sg_spi, &byte, sizeof(uint8_t));
    if (ret != ESP_OK)
    {
        printf("[%s] call spi_master_tx_rx error(%d)!!\r\n", __FUNCTION__,ret);
        return ret;
    }
    return ret; 
}

void spi_sendbytes(uint8_t *byte,uint8_t len)
{                
	for(int i = 0; i<len; i++)
		spi_sendbyte(byte[i]);
}

uint8_t spi_readbyte(void)
{                
    uint8_t regval = 0;
    esp_err_t ret = spi_read(sg_spi, &regval);
    if (ret != ESP_OK)
    {
        printf("[%s] call spi_master_tx_rx error(%d)!!\r\n", __FUNCTION__,ret);
        return ret;
    }
    return regval; 
}

void spi_readbytes(uint8_t *byte,uint8_t len)
{
  for(int i = 0; i<len; i++)
		byte[i] = spi_readbyte();
}

//-----------------------------------------------------------------------------------------------------------


void nand_protect(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x01); 	        // Read status register 2
  SR|=0x7C;										// Enable ECC-E bit
  nand_write_status_register(0x01,SR);
}

void nand_unprotect(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x01);
  SR&=0x83;
  nand_write_status_register(0x01,SR);
}

void nand_write_enable(void)
{ 
  uint8_t cmd[4] = {0};
  NAND_CS_LOW();
  cmd[0] = 0x06; 
	spi_sendbyte(cmd[0]);
  NAND_CS_HIGH();
}

void nand_Reset(void)
{
    uint8_t ret = 0;
    uint8_t cmd[4] = {0};
    
	  NAND_CS_LOW();
    cmd[0] = 0xFF;
		spi_sendbyte(cmd[0]);

    NAND_CS_HIGH();
    nand_enable_embedded_ECC();
    ret = nand_check_embedded_ECC_enable();
    if(ret != 1)     
    {
        printf("set hw ecc fail\r\n");
    }
    nand_busy_check();
}


uint32_t nand_read_JEDEC_ID(void)
{
    uint32_t JEDEC_ID = 0;
    
    uint8_t cmd[4] = {0};
    uint8_t rdat[4] = {0};

    NAND_CS_LOW();
    cmd[0] = 0x9F;
    cmd[1] = 0x00;  
		spi_sendbytes(cmd,2);
		spi_readbytes(rdat,4);

    JEDEC_ID += rdat[0];
    JEDEC_ID <<= 8;
    JEDEC_ID += rdat[1];
    JEDEC_ID <<= 8;
    JEDEC_ID += rdat[2];
    NAND_CS_HIGH();
    return JEDEC_ID;
}



void nand_LUT_set(uint16_t LBA, uint16_t PBA)
{ 
  uint8_t cmd[5] = {0};
  nand_write_enable();

  NAND_CS_LOW();
  cmd[0] = 0xA1;
  cmd[1] = LBA/0x100;
  cmd[2] = LBA%0x100;
  cmd[3] = PBA/0x100;
  cmd[4] = PBA%0x100;
	spi_sendbytes(cmd, 5);
  NAND_CS_HIGH();
}

void nand_LUT_read(uint16_t* LBA, uint16_t* PBA)
{
  uint8_t cmd[4] = {0};
  uint8_t rdata[80] = {0};

  uint16_t i = 0;
  uint16_t l_addr = 0;
  uint16_t p_addr = 0;
  
  NAND_CS_LOW();
  cmd[0] = 0xA5;
  cmd[1] = 0x00;
	spi_sendbytes(cmd, 2);
	spi_readbytes(rdata,80);

  for(i = 0; i < 20; i++)
  {
    l_addr =  rdata[i*4];
    l_addr =  l_addr << 8;
    l_addr |= rdata[i*4 + 1];
    *(LBA+i) = l_addr;

    p_addr =  rdata[i*4 +2];
    p_addr =  p_addr << 8;
    p_addr |= rdata[i*4 + 3];
    *(PBA+i) = p_addr;
  }
  NAND_CS_HIGH();

  return;
}

int nand_erase_a_block(uint8_t page_addh, uint8_t page_addl)
{
  uint8_t cmd[4] = {0};
  nand_unprotect();
  nand_write_enable();

  NAND_CS_LOW();
  cmd[0] = 0xD8;
  cmd[1] = 0x00;
  cmd[2] = page_addh;
  cmd[3] = page_addl;   
  spi_sendbytes(cmd, 4);

  NAND_CS_HIGH();

  nand_busy_check();

  if(nand_check_erase_fail_flag())
      return 1;//E-Fail
  else
      return 0;//E-Success
}


/******************************************************************************
* Function:    nand_check_erase_fail_flag
* Description:Check erase P-Fail, E-Fail bit
* Input:       
* Output:      null
* Return:      0: sucess, 1: fail
******************************************************************************/
uint8_t nand_check_erase_fail_flag(void)
{
  uint8_t SR = 0;
  
  SR = nand_read_status_register(0x03); 	        //Read status register 3
  if((SR >> 2) & 0x01)
    return 1;//E-Fail
  else
    return 0;//E-Success
}


/******************************************************************************
* Function:    nand_check_program_fail_flag
* Description:Check program P-Fail, E-Fail bit
* Input:       
* Output:      null
* Return:      0: sucess, 1: fail
******************************************************************************/
uint8_t nand_check_program_fail_flag(void)
{
  uint8_t SR = 0;
  
  SR = nand_read_status_register(0x03); 	        //Read status register 3
  if((SR >> 3) & 0x01)
    return 1;//P-Fail
  else
    return 0;//P-Success
}


uint8_t nand_check_embedded_ECC_flag(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x03); 	// Read status register 3
  return (SR&0x30)>>4;							// Check ECC-1, ECC0 bit								
}

void nand_enable_embedded_ECC(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x02); 	// Read status register 2
  SR|=0x10;										// Enable ECC-E bit
  nand_write_status_register(0x02,SR);
}

void nand_disable_embedded_ECC(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x02); 	// Read status register 2
  SR&= 0xEF;									// Disable ECC-E bit
	//1101 1111  1110 1111 = 1100 1111
  nand_write_status_register(0x02,SR);
}

uint8_t nand_check_embedded_ECC_enable(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x02);  // Read status register 2
  return (SR&0x10)>>4;
}

void nand_enable_buffer_mode(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x02); 	// Read status register 2
  SR|=0x08;										// Enable BUF bit
  nand_write_status_register(0x02,SR);
}

uint8_t nand_check_buffer_mode(void)
{
  uint8_t SR;
  
  SR = nand_read_status_register(0x02); 	// Read status register 2
  SR &= 0x08;										// Enable BUF bit
  
  return SR>>3;
}

void nand_disable_buffer_mode(void)
{
  uint8_t SR;
  SR = nand_read_status_register(0x02); 	// Read status register 2
  SR&= 0xF7;									// Disable BUF bit
  nand_write_status_register(0x02,SR);
}

uint8_t nand_read_status_register(uint8_t sr_sel)
{
  uint8_t SR = 0;
  uint8_t cmd[4] = {0};
  
  switch(sr_sel)
  {
    case 0x01:
      NAND_CS_LOW();//SR1
      cmd[0] = 0x05;
      cmd[1] = 0xA0;    
			spi_sendbytes(cmd, 2);
			spi_readbytes(&SR,1);     
      NAND_CS_HIGH();
      break;
      
    case 0x02:
      NAND_CS_LOW();
      cmd[0] = 0x05;
      cmd[1] = 0xB0;   
			spi_sendbytes(cmd, 2);
			spi_readbytes(&SR,1);           
      NAND_CS_HIGH();
      break;
      
    case 0x03:
      NAND_CS_LOW();
      cmd[0] = 0x05;
      cmd[1] = 0xC0;   
			spi_sendbytes(cmd, 2);
			spi_readbytes(&SR,1);            
      NAND_CS_HIGH();
      break;
      
    default:
      SR = 0xFF;
      break;
  }
  return SR;
}

/******************************************************************************
* Function:    nand_write_status_register
* Description:nand_write_status_register
* Input:       
* Output:      null
* Return:      
******************************************************************************/
void nand_write_status_register(uint8_t sr_sel, uint8_t wdat)
{
  uint8_t cmd[4] = {0};
  switch(sr_sel)
  {
    case 0x01:
      NAND_CS_LOW();//SR1
      cmd[0] = 0x01;
      cmd[1] = 0xA0;
      cmd[2] = wdat;  
			spi_sendbytes(cmd, 3);                 
      NAND_CS_HIGH();
      break;
      
    case 0x02:
      NAND_CS_LOW();
      cmd[0] = 0x01;
      cmd[1] = 0xB0;
      cmd[2] = wdat;    
			spi_sendbytes(cmd, 3);
      NAND_CS_HIGH();
      break;
      
    case 0x03:
      NAND_CS_LOW();
      cmd[0] = 0x01;
      cmd[1] = 0xC0;
      cmd[2] = wdat;   
			spi_sendbytes(cmd, 3);
      NAND_CS_HIGH();
      break;
      
    default:
      break;
  }
  nand_busy_check();
}

/******************************************************************************
* Function:    nand_busy_check
* Description:nand_busy_check
* Input:       
* Output:      null
******************************************************************************/
int nand_busy_check(void)
{
    uint8_t SR3 = 0xFF;//Flash Status Register 3
    uint8_t cmd[4] = {0};
    
    NAND_CS_LOW();
    while((SR3 & 0x3) != 0x00)
    {
      cmd[0] = 0x05;
      cmd[1] = 0xC0;  
			spi_sendbytes(cmd, 2);
			spi_readbytes(&SR3,1);		          
    }    
    NAND_CS_HIGH();   

    return 0;
}

/******************************************************************************
* Function:    nand_page_data_read
* Description:read out the page data to the data buffer
* Input:       
* Output:      null
******************************************************************************/
void nand_page_data_read(uint8_t page_addh, uint8_t page_addl)
{
    uint8_t cmd[4] = {0};

    NAND_CS_LOW();
    cmd[0] = 0x13;
    cmd[1] = 0x00;
    cmd[2] = page_addh;
    cmd[3] = page_addl;  
		spi_sendbytes(cmd, 4);

    NAND_CS_HIGH();
    
    nand_busy_check();
}

/******************************************************************************
* Function:    nand_normal_Read
* Description:read out data from data buffer located by col_addh,col_addl
* Input:       
* Output:      null
* Return:      
******************************************************************************/
void nand_normal_read(uint8_t col_addh, uint8_t col_addl, uint8_t* buff, uint32_t count)
{
    uint8_t cmd[4] = {0};
    
    NAND_CS_LOW();
    cmd[0] = 0x03;
    cmd[1] = col_addh;
    cmd[2] = col_addl;
    cmd[3] = 0x00;  
		spi_sendbytes(cmd, 4);
		spi_readbytes(buff,count);
    NAND_CS_HIGH(); 
}

/******************************************************************************
* Function:    nand_continuous_normal_read
* Description:read out data from data buffer start from first byte
* Input:       
* Output:      null
******************************************************************************/
void nand_continuous_normal_read(uint8_t* buff, uint32_t count)
{ 
  uint8_t cmd[4] = {0};
  
  NAND_CS_LOW();
  cmd[0] = 0x03;
  cmd[1] = 0x00;
  cmd[2] = 0x00;
  cmd[3] = 0x00; 
	spi_sendbytes(cmd, 4);
	spi_readbytes(buff,count);

  NAND_CS_HIGH();  
}


/******************************************************************************
* Function:    nand_pageprogram_pattern
* Description:step 1: load data to the page buffer,start addr:col_addh,
col_addl
* Input:       
* Output:      null
* Return:      
******************************************************************************/
void nand_page_program_pattern(uint8_t col_addh, uint8_t col_addl, const  uint8_t* program_buffer, uint32_t count)
{  
  uint8_t cmd[4] = {0};
  
  nand_write_enable();  
  NAND_CS_LOW();
  cmd[0] = 0x02;
  cmd[1] = col_addh; 
  cmd[2] = col_addl; 
	spi_sendbytes(cmd, 3);
	spi_readbytes((uint8_t *)program_buffer,count);

  NAND_CS_HIGH();    
}


/******************************************************************************
* Function:    nand_page_program_random
* Description:step 1: load data to the page buffer,start addr:col_addh,
col_addl
* Input:       
* Output:      null
******************************************************************************/
void nand_page_program_random(uint8_t col_addh, uint8_t col_addl, const uint8_t* program_buffer, uint32_t count)
{  
  uint8_t cmd[4] = {0};
  
  nand_write_enable();  
  NAND_CS_LOW();
  cmd[0] = 0x84;
  cmd[1] = col_addh; 
  cmd[2] = col_addl; 
	spi_sendbytes(cmd, 3);
	spi_readbytes((uint8_t *)program_buffer,count);
  NAND_CS_HIGH();    
}


/******************************************************************************
* Function:    nand_program_excute
* Description:step 2: load the page buffer to the physical page 
* Input:       
* Output:      null
******************************************************************************/
void nand_program_excute(uint8_t page_addh, uint8_t page_addl)
{ 
  uint8_t cmd[4] = {0};

  NAND_CS_LOW(); 
  cmd[0] = 0x10; 
  cmd[1] = 0x00;
  cmd[2] = page_addh; 
  cmd[3] = page_addl;
	spi_sendbytes(cmd, 4);

  NAND_CS_HIGH();    
  nand_busy_check();
}
